﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.IO.Compression;
using NSoup;
using NSoup.Nodes;
using NSoup.Select;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace manganelos
{
    public struct Manganelos
    {
        public string chapterName;
        public string chapterUrl;
        public string[] pages;
    }

    public class ManganelosTools
    {
        public ManganelosTools(string url)
        {
            chapterHtml = new MyHtml(url);
            myWebClient = new MyWebClient();
            chapterList = new List<Manganelos>();
            arrayDataList = new List<string>();
            chapterHtmlDoc = chapterHtml.GetWebHtmlDC();
        }


        MyHtml chapterHtml; 
        Document chapterHtmlDoc;
        MyWebClient myWebClient;
        string TITLE;
        Dictionary<string, string> restartDic;

        /// <summary>
        /// 一个用来输出信息内容的函数
        /// </summary>
        public Action<string> printAction { get; set; }


        private List<Manganelos> chapterList;
        /// <summary>
        /// 获取章节链接列表
        /// </summary>
        public List<Manganelos> ChapterList
        {
            get { return chapterList; }
        }
        
        private BlockingCollection<Manganelos> blockChapterList;
        /// <summary>
        /// 获取章节链接列表(线程锁)
        /// </summary>
        public BlockingCollection<Manganelos> BlockChapterList
        {
            get { return blockChapterList; }
        }


        private List<string> arrayDataList;
        /// <summary>
        /// 获取每一个章节中的所有图片
        /// </summary>
        public List<string> ArrayDataList
        {
            get { return arrayDataList; }
        }

        private BlockingCollection<string> blockArrayDataList;
        /// <summary>
        /// 获取每一个章节中的所有图片(线程锁)
        /// </summary>
        public BlockingCollection<string> BlockArrayDataList
        {
            get { return blockArrayDataList; }
        }


        void Print(string str)
        {
            if (printAction != null)
            {
                printAction(str);
            }
        }

        /// <summary>
        /// 检查错误信息
        /// </summary>
        /// <param name="info">错误信息码</param>
        /// <returns>没问题返回true，否则返回false</returns>
        public bool ChackNumber(string info)
        {
            string erro = ChackErro(info);
            if (!string.IsNullOrEmpty(erro))
            {
                Print(erro);
                return false;
            }
            return true;

            string ChackErro(string message)
            {
                switch (message)
                {
                    case "e":
                        return "警告：出现异常错误！！";
                    case "0":
                        return "查找不到章节列表 chapter-list";
                    case "1":
                        return "查找不到章节列表的 a";
                    case "2":
                        return "无法获取 arraydata";
                    case "3":
                        return "chapterHtmlDoc 为空";
                    case "4":
                        return "???";
                    case "5":
                        return "???";
                    default:
                        return string.Empty;
                }
            }
        }

        void TryReStart(string dirPath)
        {
            Print($"《《《《《《 Info: Step5 尝试重新下载下载异常的图片》》》》》》");
            string logPath = dirPath + @"/下载失败记录/log.text";
            StreamWriter writer;
            if (!File.Exists(logPath)) 
            {
                writer = File.CreateText(logPath);
            }
            else
            {
                
            }
            foreach (var item in restartDic)
            {
                if (!DownForPages(item.Key, item.Value))
                {
                    Print($"Erro: 尝试重新下载下失败");
                }
            }
        }

        /// <summary>
        /// 下载所有章节中的图片
        /// </summary>
        /// <param name="dirPath"></param>
        public void DownForAllChapter(string dirPath)
        {
            if (chapterList.Count == 0) return;
            bool key = ChackNumber(SetTitle());
            if (!key) return;
            dirPath = dirPath + @"/" + TITLE;
            if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
            string pageUrl, fliePath, name;
            string[] temp1;
            restartDic = new Dictionary<string, string>();
            var watchH = new System.Diagnostics.Stopwatch();
            for (int i = 0; i < chapterList.Count; i++)
            {
                Print($"《《《《《《 Info: Step4 正在下载第 {i} 章内容》》》》》》");
                watchH.Reset();
                watchH.Start();
                for (int x = 0; x < chapterList[i].pages.Length; x++)
                {
                    if (x==0)
                    {
                        fliePath = dirPath + @"/" + chapterList[i].chapterName;
                        if (!Directory.Exists(fliePath)) Directory.CreateDirectory(fliePath);
                    }
                    pageUrl = chapterList[i].pages[x];
                    temp1 = pageUrl.Split('/');
                    name = temp1[temp1.Length-1];
                    temp1 = name.Split('.');
                    name = $"{x}.{temp1[temp1.Length - 1]}";
                    fliePath = dirPath + @"/" + chapterList[i].chapterName + @"/" + name;
                    if (!File.Exists(fliePath))
                    {
                        if (!DownForPages(pageUrl, fliePath))
                        {
                            restartDic.Add(pageUrl, fliePath);
                        }
                    }
                }
                watchH.Stop();
                Print($"《《《《《《 Info: 共耗时 {watchH.ElapsedMilliseconds/1000} 秒 》》》》》》");
            }
            if (restartDic.Count > 0)
            {
                TryReStart(dirPath);
            }
            Close();
        }

        /// <summary>
        /// 下载单张图片
        /// </summary>
        /// <param name="pageUrl">图片链接</param>
        /// <param name="filePath">本地保存地址</param>
        bool DownForPages(string pageUrl ,string filePath)
        {
            return myWebClient.DownFile(pageUrl, filePath);
        }

        void Close()
        {
            myWebClient.webDispose();
        }

        /// <summary>
        /// 获取每个章节的图片
        /// </summary>
        /// <param name="chapterUrl">章节链接</param>
        /// <returns>没问题返回章节中的所有图片链接，有问题返回空字符串</returns>
        string GetPageList(string chapterUrl)
        {
            if (string.IsNullOrEmpty(chapterUrl)) return string.Empty;
            MyHtml pageHtml = new MyHtml(chapterUrl);
            Document pageDoc = pageHtml.GetWebHtmlDC();
            Element arraydata = pageDoc.GetElementById("arraydata");
            if (arraydata == null) return string.Empty;
            else return arraydata.Html();
        }

        /// <summary>
        /// 设置漫画标题
        /// </summary>
        /// <returns></returns>
        public string SetTitle()
        {
            Print($"《《《《《《 Info: Step3 正在解析漫画标题 》》》》》》");
            if (chapterHtmlDoc == null) return "3";
            Elements titles = chapterHtmlDoc.GetElementsByClass("title-manga");
            if (titles == null) 
            {
                var array = chapterHtml.Url.Split('/');
                TITLE = array[array.Length-1];
            }
            else
            {
                string temp = titles[0].Text();
                if (temp == null)
                {
                    var array = chapterHtml.Url.Split('/');
                    TITLE = array[array.Length - 1];
                }
                else
                {
                    TITLE = temp;
                }
            }
            Print($"《《《《《《 Info: 漫画标题 {TITLE} 》》》》》》");
            return string.Empty;
        }

        /// <summary>
        /// 设置章节列表
        /// </summary>
        /// <returns>没问题返回空字符串，有问题返回错误码</returns>
        public string SetManganelos()
        {
            try
            {
                Print($"《《《《《《 Info: Step1 正在解析所有章节 》》》》》》");
                Elements chapters = chapterHtmlDoc.GetElementsByClass("chapter-list");
                if (chapters == null) return "0";
                Elements aList = chapters[1].GetElementsByTag("a");
                if (aList == null) return "1";
                Manganelos manganelos;
                string chapterName = default;
                string chapterUrl = default;
                string pages = default;
                string key = string.Empty;
                Print($"《《《《《《 Info: Step2 正在解析章节中的内容 》》》》》》");
                var watchH = new System.Diagnostics.Stopwatch();
                watchH.Start();
                for (int i = 0; i < aList.Count; i++)
                {
                    manganelos = new Manganelos();
                    //提取章节标题
                    chapterName = aList[i].Text();
                    if (string.IsNullOrEmpty(chapterName)) chapterName = string.Empty;
                    manganelos.chapterName = chapterName;
                    //提取章节链接
                    chapterUrl = aList[i].Attr("href");
                    if (string.IsNullOrEmpty(chapterUrl)) 
                    {
                        chapterUrl = string.Empty;
                        key = "2";
                    }
                    manganelos.chapterUrl = chapterUrl;
                    //提取章节中的所有图片链接
                    pages = GetPageList(chapterUrl);
                    if (string.IsNullOrEmpty(pages))
                    {
                        Print($"Erro: 【 {i} 章 】解析异常");
                        manganelos.pages = default;
                        key = "2";
                    }
                    else manganelos.pages = pages.Split(',');
                    //保存到章节列表中
                    chapterList.Add(manganelos);
                }
                watchH.Stop();
                Print($"《《《《《《 Info: 共获取 {chapterList.Count} 章 》》》》》》");
                Print($"《《《《《《 Info: 共耗时 {watchH.ElapsedMilliseconds/1000} 秒 》》》》》》");
                return key;
            }
            catch
            {
                return "e";
            }
        }
    }

    public class MyWebClient
    {
        public MyWebClient()
        {
            webClient = new WebClient();
            webHeader = new WebHeaderCollection();
            webHeader.Set(HttpRequestHeader.Accept, "*/*");
            webHeader.Set(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
            webHeader.Set(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
            webHeader.Set(HttpRequestHeader.UserAgent, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36");
            webHeader.Set(HttpRequestHeader.CacheControl, "no-cache, private");
            webHeader.Set(HttpRequestHeader.AcceptLanguage, "*/*");
            webHeader.Set(HttpRequestHeader.Connection, "keep-alive");
            webClient.Headers = webHeader;
        }

        public WebClient webClient { get; set; }
        WebHeaderCollection webHeader { get; set; }

        /// <summary>
        /// 下载单张图片
        /// </summary>
        /// <param name="jpgUrl">图片地址</param>
        /// <param name="filePath">本地保存地址</param>
        /// <returns>下载完成返回 true ，否则返回 false </returns>
        public bool DownFile(string jpgUrl,string filePath)
        {
            try
            {
                webClient.DownloadFileTaskAsync(jpgUrl, filePath);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public void webDispose()
        {
            webClient?.Dispose();
        }
    }

    public class MyHtml
    {
        public MyHtml(string url)
        {
            Url = url;
            CookiesContainer = default;
            HtmlDocument = default;
        }

        public CookieContainer CookiesContainer { get; set; }//定义当前 Cookie 容器
        public Document HtmlDocument { get; set; }//定义当前 HtmlDocument 容器
        public string Url { get; set; } //定义当前 URL 容器

        /// <summary>
        /// 获取 Document 对象
        /// </summary>
        public Document GetWebHtmlDC()
        {
            HtmlDocument = NSoupClient.Parse(GetWebHtml());
            return HtmlDocument;
        }

        /// <summary>
        /// 自动根据 contentType 获取对应编码格式 Html 源码字符串
        /// （兼容多种解压方式（gzip,deflate,源码）和编码格式（gbk,gb2312等多种编码格式））
        /// </summary>
        /// <returns>返回 Html 字符串源码</returns>
        public string GetWebHtml()
        {
            return GetWebHtml("");
        }

        /// <summary>
        /// 获取指定编码格式 Html 源码字符串
        /// （兼容多种解压方式（gzip,deflate,源码）和编码格式（gbk,gb2312等多种编码格式））
        /// </summary>
        /// <returns>返回 Html 字符串源码</returns>
        public string GetWebHtml(string myencoding)
        {
            var pageSource = string.Empty;
            HttpWebRequest request = WebRequest.CreateHttp(Url);
            request.Accept = "*/*";
            request.ServicePoint.Expect100Continue = false;//加快载入速度
            request.ServicePoint.UseNagleAlgorithm = false;//禁止Nagle算法加快载入速度
            request.AllowWriteStreamBuffering = false;//禁止缓冲加快载入速度
            request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");//定义gzip压缩页面支持
            request.ContentType = "application/x-www-form-urlencoded";//定义文档类型及编码
            request.AllowAutoRedirect = false;//禁止自动跳转
                                              //设置User-Agent，伪装成Google Chrome浏览器
            request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36";
            request.Timeout = 5000;//定义请求超时时间为5秒
            request.KeepAlive = true;//启用长连接
            request.Method = "GET";//定义请求方式为GET              
            request.CookieContainer = this.CookiesContainer;//附加Cookie容器
            request.ServicePoint.ConnectionLimit = int.MaxValue;//定义最大连接数
            
            using (var response = (HttpWebResponse)request.GetResponse()) //获取请求响应
            {
                foreach (Cookie cookie in response.Cookies) this.CookiesContainer.Add(cookie);//将Cookie加入容器，保存登录状态
                string contentType = default;
                if (string.IsNullOrEmpty(myencoding)) contentType = response.ContentType.ToLower();
                else contentType = myencoding;

                if (response.ContentEncoding.ToLower().Contains("gzip"))//解压
                {
                    using (GZipStream stream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress))
                    {
                        Console.WriteLine($"==================== gzip ====================");
                        pageSource = SwitchEncoding(contentType, stream);
                    }
                }
                else if (response.ContentEncoding.ToLower().Contains("deflate"))//解压
                {
                    using (DeflateStream stream = new DeflateStream(response.GetResponseStream(), CompressionMode.Decompress))
                    {
                        Console.WriteLine($"==================== deflate ====================");
                        pageSource = SwitchEncoding(contentType, stream);
                    }
                }
                else
                {
                    using (Stream stream = response.GetResponseStream())//原始
                    {
                        Console.WriteLine($"==================== 原始 ====================");
                        pageSource = SwitchEncoding(contentType, stream);
                    }
                }
            }
            request.Abort();
            return pageSource;
        }

        /// <summary>
        /// 选择对应的编码解析HTML
        /// </summary>
        /// <param name="contentType">response 的 contentType 属性值</param>
        /// <param name="stream">读取服务器响应的流</param>
        /// <returns>返回解析后的 Html 字符串</returns>
        string SwitchEncoding(string contentType, Stream stream)
        {
            if (contentType.Contains("charset=")) contentType = System.Text.RegularExpressions.Regex.Split(contentType, "charset=")[1].ToLower();
            Console.WriteLine($"==================== 编码：[{contentType}] ====================");
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            byte[] buff = new byte[1024 * 1024 * 4];
            int n = stream.Read(buff, 0, buff.Length);
            Encoding encoding;

            switch (contentType.ToLower())
            {
                case "gbk":
                    encoding = Encoding.GetEncoding("GBK");
                    break;
                case "gb2312":
                    encoding = Encoding.GetEncoding("GB2312");
                    break;
                case "utf-8":
                    encoding = Encoding.UTF8;
                    break;
                case "iso-8859-1":
                    encoding = Encoding.GetEncoding("ISO-8859-1");
                    break;
                case "ascii":
                    encoding = Encoding.ASCII;
                    break;
                case "unicode":
                    encoding = Encoding.Unicode;
                    break;
                case "utf-32":
                    encoding = Encoding.UTF32;
                    break;
                default:
                    encoding = Encoding.Default;
                    break;
            }
            return encoding.GetString(buff, 0, n);
        }


        /// <summary>
        /// 获取下载到本地的Html文件源码字符串内容
        /// </summary>
        /// <param name="path">Html文件地址</param>
        /// <returns>返回 Html 字符串，如无内容，返回空字符串</returns>
        public string GetFileHtml(string path)
        {
            try
            {
                if (File.Exists(path)) return File.ReadAllText(path);
                else return default;

            }
            catch
            {
                return default;
            }
        }

        /// <summary>
        /// 下载Html到本地
        /// </summary>
        /// <param name="path">保存路径</param>
        public void SaveHtml(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                Console.WriteLine("路径不能为空！");
                return;
            }
            try
            {
                string html = this.GetWebHtml();
                StreamWriter writer = File.CreateText(path);
                writer.AutoFlush = true;
                writer.Write(html);
                writer.Close();
                Console.WriteLine("保存完毕！");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}
